package cn.bincker.mybatis.encrypt.data.migration.impl;

import cn.bincker.mybatis.encrypt.core.EncryptExecutor;
import cn.bincker.mybatis.encrypt.data.migration.entity.ColumnInfo;
import cn.bincker.mybatis.encrypt.data.migration.entity.MigrateTable;
import cn.bincker.mybatis.encrypt.data.migration.exception.MigrateException;
import lombok.extern.slf4j.Slf4j;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.List;
import java.util.stream.Collectors;

@SuppressWarnings({"SqlDialectInspection", "SqlSourceToSinkFlow", "unused"})
@Slf4j
public class CommonMigrateDatabase extends AbstractMigrateDatabase{
    public CommonMigrateDatabase(DataSource srcDataSource, DataSource distDataSource, EncryptExecutor encryptExecutor, boolean migrateAllTable, int batchInsertRows, boolean failContinue) {
        super(srcDataSource, distDataSource, encryptExecutor, migrateAllTable, batchInsertRows, failContinue);
    }

    @Override
    protected void beforeInsert(Connection conn, MigrateTable table) {
    }

    @Override
    protected String buildInsertSql(MigrateTable table) {
        return "insert into " + table.getDistTableSqlName() + " (" +
                table.getColumns().stream().map(ColumnInfo::getSqlName).collect(Collectors.joining(",")) +
                ") values (" +
                table.getColumns().stream().map(c -> "?").collect(Collectors.joining(",")) +
                ")";
    }

    @Override
    protected PreparedStatement buildExistsQuery(Connection connection, MigrateTable table, List<Object[]> rows) throws SQLException {
        var keys = table.getKeys();
        String keyFields = keys.stream().map(ColumnInfo::getSqlName).collect(Collectors.joining(","));
        String sql = "select " + keyFields + " from " + table.getDistTableSqlName() + " where ";
        if (keys.size() == 1){
            sql += keyFields + " in " + rows.stream().map(r->"?").collect(Collectors.joining(",", "(", ")"));
        }else{
            sql += "(" + keyFields + ") in (" +
                    rows.stream().map(row -> keys.stream().map(k -> "?").collect(Collectors.joining(",", "(", ")"))).collect(Collectors.joining(",")) +
                    ")";
        }
        var statement = connection.prepareStatement(sql);
        for (int j = 0; j < rows.size(); j++) {
            Object[] row = rows.get(j);
            for (int i = 0; i < keys.size(); i++) {
                ColumnInfo key = keys.get(i);
                statement.setObject(j * keys.size() + i + 1, row[key.getIndex()]);
            }
        }
        return statement;
    }

    @Override
    protected String buildCountSql(String tableName) {
        return "select count(*) from " + tableName;
    }

    @Override
    protected String buildPageQuerySql(MigrateTable table, long page) {
        var keys = table.getKeys();
        if (keys.size() == 1){
            var key = keys.get(0);
            return "select " + table.getColumns().stream().map(ColumnInfo::getSqlName).collect(Collectors.joining(",")) +
                    " from " + table.getSrcTableSqlName() +
                    " where " + key.getSqlName() + " >= (select " + key.getSqlName() + " from " + table.getSrcTableSqlName() + " order by " + key.getSqlName() + " limit 1 OFFSET " + page * getPageSize() + ")" +
                    " limit " + getPageSize();
        }
        return "select " + table.getColumns().stream().map(ColumnInfo::getSqlName).collect(Collectors.joining(",")) +
                " from " + table.getSrcTableSqlName() +
                (keys.isEmpty() ? "" : " order by " + keys.stream().map(ColumnInfo::getSqlName).collect(Collectors.joining(","))) +
                " limit " + getPageSize() + " OFFSET " + page * getPageSize();
    }

    @Override
    protected String createDistTableDDL(MigrateTable table) {
        return "create table " + table.getDistTableSqlName() +
                table.getColumns().stream().map(c->
                        c.getSqlName() + " " +
                                (
                                        c.isEncrypt() ? "blob " :
                                                c.getType() + (c.getSize() == null ? " " : "(" + c.getSize() + ") ")
                                ) +
                                (c.isPrimaryKey() ? "primary key " : "")
                ).collect(Collectors.joining(",\n", "(\n", "\n);"));
    }

    @Override
    protected int getPageSize() {
        return 1000;
    }

    @Override
    protected void checkEncryptColumn(Connection connection, MigrateTable table, ColumnInfo column) {
        if (("bytea".equalsIgnoreCase(column.getType()) || "blob".equalsIgnoreCase(column.getType())) && column.getSize() == null){
            return;
        }
        try (var statement = connection.createStatement()){
            String sql = "alter table " + table.getDistTableSqlName() + " modify " + column.getSqlName() + " blob";
            log.info("alter table column: {}", sql);
            statement.execute(sql);
        } catch (SQLException e) {
            throw new MigrateException(e);
        }
    }

    @Override
    protected String buildAddEncryptColumnSql(MigrateTable table, ColumnInfo column) {
        return "alter table " + table.getDistTableSqlName() + " add column " + column.getName() + " blob";
    }
}
